package primitives;

import javax.vecmath.Vector3d;

import essential.Axis;
import essential.Ray;
import essential.RayCastResult;

public class AxisAlignedBoundingBox {
	//Box edges
	Vector3d a;
	Vector3d b;

	static double getMinX(Vector3d vertices[]) {
		double ret = Double.POSITIVE_INFINITY;
		
		for (int i = 0; i < vertices.length; i++)
			ret = vertices[i].x < ret? vertices[i].x : ret;

		return ret;
	}

	static double getMaxX(Vector3d vertices[]) {
		double ret = Double.NEGATIVE_INFINITY;
		
		for (int i = 0; i < vertices.length; i++) {
			ret = vertices[i].x > ret? vertices[i].x : ret;
		}		
		
		return ret;
	}
	
	static double getMinY(Vector3d vertices[]) {
		double ret = Double.POSITIVE_INFINITY;
		
		for (int i = 0; i < vertices.length; i++)
			ret = vertices[i].y < ret? vertices[i].y : ret;

		return ret;
	}

	static double getMaxY(Vector3d vertices[]) {
		double ret = Double.NEGATIVE_INFINITY;

		for (int i = 0; i < vertices.length; i++) {
			ret = vertices[i].y > ret? vertices[i].y : ret;
		}		

		return ret;
	}

	static double getMinZ(Vector3d vertices[]) {
		double ret = Double.POSITIVE_INFINITY;

		for (int i = 0; i < vertices.length; i++)
			ret = vertices[i].z < ret? vertices[i].z : ret;

		return ret;
	}

	static double getMaxZ(Vector3d vertices[]) {
		double ret = Double.NEGATIVE_INFINITY;

		for (int i = 0; i < vertices.length; i++) {
			ret = vertices[i].z > ret? vertices[i].z : ret;
		}

		return ret;
	}

	public AxisAlignedBoundingBox(Vector3d vertices[]) {
		this(
			new Vector3d(getMinX(vertices), getMinY(vertices), getMinZ(vertices)),
			new Vector3d(getMaxX(vertices), getMaxY(vertices), getMaxZ(vertices))
			);
	}
	
	public AxisAlignedBoundingBox(Vector3d a, Vector3d b) {
		
		//	  B__________C
		//	  /|        /|
		//	F/_|______G/ |
		//	 | |      |  |
		//	 |A|______|__|D  y ^
		//	 | /      | /      |
		//	 |/_______|/       +-->
		//	E         H     z /   x
		this.a = new Vector3d(a);
		this.b = new Vector3d(b);
	}
	
	public AxisAlignedBoundingBox() {
		this.a = new Vector3d();
		this.b = new Vector3d();
	}

	public Vector3d getA() {
		return a;
	}

	public void setA(Vector3d a) {
		this.a = a;
	}

	public Vector3d getB() {
		return b;
	}

	public void setB(Vector3d b) {
		this.b = b;
	}
	
	public void sum(AxisAlignedBoundingBox aabb) {
		for (int i=0; i<Axis.values().length; i++) {
			Axis axis = Axis.values()[i];
			
			Axis.setComponent(a, axis,
					Math.min(
							Axis.getComponent(a, axis),
							Axis.getComponent(aabb.a, axis)));

			Axis.setComponent(b, axis,
					Math.max(
							Axis.getComponent(b, axis),
							Axis.getComponent(aabb.b, axis)));
		}
	}

	public void split(Axis axis, double div, AxisAlignedBoundingBox aabbR, AxisAlignedBoundingBox aabbL) {
		aabbL.setA(new Vector3d(a));
		aabbR.setB(new Vector3d(b));
		aabbL.setB(new Vector3d(b));
		aabbR.setA(new Vector3d(a));

		Axis.setComponent(aabbL.getA(), axis, div);
		Axis.setComponent(aabbR.getB(), axis, div);
	}

	public RayCastResult intersection(Ray r)
	{
		Vector3d o = r.getOrigin();
		Vector3d d = r.getDirection();
		
		double xa = a.x - o.x; 
		double ya = a.y - o.y; 
		double za = a.z - o.z; 
		double xb = b.x - o.x;
		double yb = b.y - o.y;
		double zb = b.z - o.z;
		
		switch(r.signs) {
		case MMM:
			// case MMM: side(R,HD) < 0 or side(R,FB) > 0 or side(R,EF) > 0 or side(R,DC) < 0 or side(R,CB) < 0 or side(R,HE) > 0 to miss
			if ((o.x < a.x) || (o.y < a.y) || (o.z < a.z))
				return RayCastResult.MISS;

			if(	(d.x * ya - d.y * xb < 0) ||
				(d.x * yb - d.y * xa > 0) ||
				(d.x * zb - d.z * xa > 0) ||
				(d.x * za - d.z * xb < 0) ||
				(d.y * za - d.z * yb < 0) ||
				(d.y * zb - d.z * ya > 0))
				return RayCastResult.MISS;

			return RayCastResult.HIT;
		case MMP:
			// case MMP: side(R,HD) < 0 or side(R,FB) > 0 or side(R,HG) > 0 or side(R,AB) < 0 or side(R,DA) < 0 or side(R,GF) > 0 to miss
			if ((o.x < a.x) || (o.y < a.y) || (o.z > b.z))
				return RayCastResult.MISS;

			if(	(d.x * ya - d.y * xb < 0) ||
				(d.x * yb - d.y * xa > 0) ||
				(d.x * zb - d.z * xb > 0) ||
				(d.x * za - d.z * xa < 0) ||
				(d.y * za - d.z * ya < 0) ||
				(d.y * zb - d.z * yb > 0))
				return RayCastResult.MISS;

			return RayCastResult.HIT;
		case MPM:
			// case MPM: side(R,EA) < 0 or side(R,GC) > 0 or side(R,EF) > 0 or side(R,DC) < 0 or side(R,GF) < 0 or side(R,DA) > 0 to miss
			if ((o.x < a.x) || (o.y > b.y) || (o.z < a.z))
				return RayCastResult.MISS;

			if(	(d.x * ya - d.y * xa < 0) ||
				(d.x * yb - d.y * xb > 0) ||
				(d.x * zb - d.z * xa > 0) ||
				(d.x * za - d.z * xb < 0) ||
				(d.y * zb - d.z * yb < 0) ||
				(d.y * za - d.z * ya > 0))
				return RayCastResult.MISS;

			return RayCastResult.HIT;
		case MPP:
			// case MPP: side(R,EA) < 0 or side(R,GC) > 0 or side(R,HG) > 0 or side(R,AB) < 0 or side(R,HE) < 0 or side(R,CB) > 0 to miss
			if ((o.x < a.x) || (o.y > b.y) || (o.z > b.z))
				return RayCastResult.MISS;

			if(	(d.x * ya - d.y * xa < 0) ||
				(d.x * yb - d.y * xb > 0) ||
				(d.x * zb - d.z * xb > 0) ||
				(d.x * za - d.z * xa < 0) ||
				(d.y * zb - d.z * ya < 0) ||
				(d.y * za - d.z * yb > 0))
				return RayCastResult.MISS;

			return RayCastResult.HIT;
		case PMM:
			// case PMM: side(R,GC) < 0 or side(R,EA) > 0 or side(R,AB) > 0 or side(R,HG) < 0 or side(R,CB) < 0 or side(R,HE) > 0 to miss

			if ((o.x > b.x) || (o.y < a.y) || (o.z < a.z))
				return RayCastResult.MISS;

			if(	(d.x * yb - d.y * xb < 0) ||
				(d.x * ya - d.y * xa > 0) ||
				(d.x * za - d.z * xa > 0) ||
				(d.x * zb - d.z * xb < 0) ||
				(d.y * za - d.z * yb < 0) ||
				(d.y * zb - d.z * ya > 0))
				return RayCastResult.MISS;

			return RayCastResult.HIT;
		case PMP:
			// case PMP: side(R,GC) < 0 or side(R,EA) > 0 or side(R,DC) > 0 or side(R,EF) < 0 or side(R,DA) < 0 or side(R,GF) > 0 to miss
			if ((o.x > b.x) || (o.y < a.y) || (o.z > b.z))
				return RayCastResult.MISS;

			if(	(d.x * yb - d.y * xb < 0) ||
				(d.x * ya - d.y * xa > 0) ||
				(d.x * za - d.z * xb > 0) ||
				(d.x * zb - d.z * xa < 0) ||
				(d.y * za - d.z * ya < 0) ||
				(d.y * zb - d.z * yb > 0))
				return RayCastResult.MISS;

			return RayCastResult.HIT;
		case PPM:
			// case PPM: side(R,FB) < 0 or side(R,HD) > 0 or side(R,AB) > 0 or side(R,HG) < 0 or side(R,GF) < 0 or side(R,DA) > 0 to miss
			if ((o.x > b.x) || (o.y > b.y) || (o.z < a.z))
				return RayCastResult.MISS;

			if(	(d.x * yb - d.y * xa < 0) ||
				(d.x * ya - d.y * xb > 0) ||
				(d.x * za - d.z * xa > 0) ||
				(d.x * zb - d.z * xb < 0) ||
				(d.y * zb - d.z * yb < 0) ||
				(d.y * za - d.z * ya > 0))
				return RayCastResult.MISS;

			return RayCastResult.HIT;
		case PPP:
			// case PPP: side(R,FB) < 0 or side(R,HD) > 0 or side(R,DC) > 0 or side(R,EF) < 0 or side(R,HE) < 0 or side(R,CB) > 0 to miss
			if ((o.x > b.x) || (o.y > b.y) || (o.z > b.z))
				return RayCastResult.MISS;

			if(	(d.x * yb - d.y * xa < 0) ||
				(d.x * ya - d.y * xb > 0) ||
				(d.x * za - d.z * xb > 0) ||
				(d.x * zb - d.z * xa < 0) ||
				(d.y * zb - d.z * ya < 0) ||
				(d.y * za - d.z * yb > 0))
				return RayCastResult.MISS;

			return RayCastResult.HIT;
			default:
				return RayCastResult.MISS;
		}
	}
	
	public String toString() {
		return "AABB(" + a + ", " + b +")";
	}
}

